Skip to content

feat(dns): add Hetzner DNS provider - #5940

Open
outeiroDev wants to merge 1 commit into
Dokploy:canaryfrom
outeiroDev:feat/hetzner-dns-provider
Open

outeiroDev wants to merge 1 commit into
Dokploy:canaryfrom
outeiroDev:feat/hetzner-dns-provider

Conversation

@outeiroDev

@outeiroDev outeiroDev commented Nov 7, 2026 •

Copy link
Copy Markdown
Contributor

What is this PR about?

Adds Hetzner as a DNS provider, using the Zones API of the Hetzner Cloud API (https://api.hetzner.cloud/v1) with a project API token (Read & Write).

  • Records ↔ RRSets: Hetzner groups records by name + type, so it follows the Route53 approach: one Dokploy record per RRSet, id <name>/<type> (Hetzner's own RRSet id), multiple values one per line.
  • Zones: only primary-mode zones are listed (secondary zones are fed by AXFR and reject writes). Zones are addressed by name, which the API accepts in place of the id.
  • Writes: upsert uses add_records (creates the RRSet if missing, appends only new values); editing in place uses set_records + change_ttl; renaming creates the new RRSet and deletes the old one.
  • TXT values: quoted, inner quotes escaped and split into 255-char chunks, mirroring FormatTXTRecord / ParseTXTRecord from hcloud-go's zoneutil. Shown unquoted in Dokploy.
  • Pagination: follows meta.pagination.next_page (max 50 per page).
  • Icon from simple-icons; migration only adds hetzner to the DnsProviderType enum.

Tests in __test__/dns/hetzner.test.ts cover zone listing and pagination, record mapping, apex @, TXT formatting, upsert merge, update/rename, delete and error messages.

Checklist

Before submitting this PR, please make sure that:

  • You created a dedicated branch based on the canary branch.
  • You have read the suggestions in the CONTRIBUTING.md file https://github.com/Dokploy/dokploy/blob/canary/CONTRIBUTING.md#pull-request
  • You have tested this PR in your local instance. If you have not tested it yet, please do so before submitting. This helps avoid wasting maintainers' time reviewing code that has not been verified by you.

Issues related (if applicable)

closes #5939

Screenshots (if applicable)

RetriggerView in GreptileConfidence Score: 3/5

The PR needs fixes before merging because editing records with several values and changing records to or from CNAME are broken.

Summary

Adds Hetzner DNS support through the Hetzner Cloud API.

  • Dokploy can manage records in Hetzner primary zones.
  • Hetzner is available as a DNS provider in settings.

Reviews (1) · Last reviewed commit: "feat(dns): add Hetzner DNS provider" · Reviewed by Greptile

Manage records in Hetzner DNS zones through the Zones API of the
Hetzner Cloud API. Like Route53, each Dokploy record maps to an RRSet,
and TXT values are quoted and chunked as hcloud-go's zoneutil does.
@outeiroDev
outeiroDev force-pushed the feat/hetzner-dns-provider branch from c4e72b9 to cc7e5b1 Compare November 7, 2026 20:38
Comment on lines +238 to +240
content: rrset.records
.map((record) => unquoteValue(rrset.type, record.value))
.join("\n"),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Editing joins separate values

Hetzner joins every value in an RRSet with newlines, but DnsRecordPanel only enables its multiline editor for route53. A Hetzner record with several values opens in a single-line input. Changing that field removes the line breaks before set_records replaces the whole RRSet, potentially replacing separate TXT values with one joined value or making address records invalid. Enable the multiline editor for hetzner too.

content: rrset.records
.map((record) => unquoteValue(rrset.type, record.value))
.join("\n"),
ttl: rrset.ttl ?? DEFAULT_TTL,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Editing changes inherited TTLs

null means the RRSet inherits its zone's TTL, not that its TTL is 300. If the zone's default is 3600, this displays 300 instead. The editor sends that number back unchanged, and updateRecord writes an explicit TTL of 300 even when the user only changed the content. Read the zone's actual default and preserve inherited TTLs when editing.

Comment on lines +140 to +143
const escaped = value.replaceAll('"', '\\"');
const chunks: string[] = [];
for (let i = 0; i < escaped.length; i += TXT_CHUNK_SIZE) {
chunks.push(`"${escaped.slice(i, i + TXT_CHUNK_SIZE)}"`);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 TXT escaping changes values

formatTxtValue escapes quotes but leaves literal backslashes untouched, then splits the escaped text at arbitrary positions. A value containing 254 a characters followed by "b puts the escape backslash at the end of the first chunk, where it escapes the closing quote. A plain value such as a\b also loses its backslash through parseTxtValue.

Escape backslashes and quotes together, keep each escape sequence inside one chunk, and add tests for both cases.

Comment on lines +286 to +294
await hzFetch(config, `/zones/${encodeURIComponent(zoneId)}/rrsets`, {
method: "POST",
body: JSON.stringify({
name,
type: record.type,
ttl: record.ttl ?? null,
records,
}),
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Changing CNAME records fails

Changing an existing A or AAAA record to CNAME keeps the same name but enters this create-before-delete branch. The new CNAME cannot coexist with the old address records, so creation fails before the old RRSet is removed. Changing CNAME to A or AAAA has the same problem. Handle these same-name type changes separately, with recovery if creating the replacement fails.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add DNS provider support for Hetzner

1 participant